﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects.DataClasses;
using System.Linq.Expressions;
using System.Reflection;

namespace IRC.IRepairClouds.DataLayer
{
    public static class EnumExtensions
    {
        private static void CheckEnum<T>()
        {
            CheckEnum(typeof(T));
        }

        private static void CheckEnum(Type type)
        {
            if (!type.IsEnum)
                throw new ArgumentException(string.Format("Type '{0}' is not an enum", type.FullName));
        }

        public static bool IsMatch<T>(this T value, T flag) where T : struct
        {
            CheckEnum<T>();
            long lValue = Convert.ToInt64(value);
            long lFlag = Convert.ToInt64(flag);
            return (lValue & lFlag) == lFlag;
        }

        public static bool IsAnyMatch<T>(this T value, T flag) where T : struct
        {
            CheckEnum<T>();
            long lValue = Convert.ToInt64(value);
            long lFlag = Convert.ToInt64(flag);
            return (lValue & lFlag) != 0;
        }

        public static IEnumerable<T> Unflags<T>(this T value) where T : struct
        {
            CheckEnum<T>();
            long lValue = Convert.ToInt64(value);

            foreach (var i in GetEnumValues<T>(typeof(T), null)) {
                long lFlag = Convert.ToInt64(i);
                if ((lValue & lFlag) != 0) {
                    yield return i;
                }
            }
        }

        public static IEnumerable<Tuple<string, T>> GetEnumNamesValues<T>(this Type type)
        {
            type.ThrowIfNull("type");
            CheckEnum<T>();
            if (!typeof(T).Equals(type))
                throw new ArgumentOutOfRangeException("type", "Nust be type T");

            object enumInst = Activator.CreateInstance(type);
            FieldInfo[] fia = type.GetFields();
            foreach (FieldInfo fi in fia) {
                if (fi.Name == "value__")
                    continue;
                T val = (T)fi.GetValue(enumInst);
                yield return new Tuple<string, T>(fi.Name, val);
            }
        }

        public static IEnumerable<string> GetEnumNames(this Type type)
        {
            type.ThrowIfNull("type");
            CheckEnum(type);

            object enumInst = Activator.CreateInstance(type);
            FieldInfo[] fia = type.GetFields();
            foreach (FieldInfo fi in fia.OrderBy(i => i.Name)) {
                if (fi.Name == "value__")
                    continue;
                yield return fi.Name;
            }
        }

        public static IEnumerable<string> GetEnumNames<T>(this Type type, IEnumerable<T> exclude)
        {
            if (!typeof(T).Equals(type))
                throw new ArgumentOutOfRangeException("type", "Nust be type T");

            HashSet<T> exc = exclude != null ? new HashSet<T>(exclude) : new HashSet<T>();
            foreach (var i in GetEnumNamesValues<T>(type)) {
                if (!exc.Contains(i.Item2))
                    yield return i.Item1;
            }
        }

        public static IEnumerable<T> GetEnumValues<T>(this Type type)
        {
            foreach (var i in GetEnumNamesValues<T>(type))
                yield return i.Item2;
        }

        public static IEnumerable<T> GetEnumValues<T>(this Type type, IEnumerable<T> exclude)
        {
            if (!typeof(T).Equals(type))
                throw new ArgumentOutOfRangeException("type", "Nust be type T");

            HashSet<T> exc = exclude != null ? new HashSet<T>(exclude) : new HashSet<T>();
            foreach (var i in GetEnumNamesValues<T>(type)) {
                if (!exc.Contains(i.Item2))
                    yield return i.Item2;
            }
        }
    }
}
